package com.xixibaby.redis.service;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
import com.xixibaby.core.dict.AdminDict;
import com.xixibaby.core.dict.DictMapping;
import com.xixibaby.core.dict.SuperDictTool;
import com.xixibaby.core.Assert;
import com.xixibaby.redis.mapper.AdminDictMapper;
import com.zengtengpeng.operation.RedissonCollection;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 */
@Slf4j
public class RedisDictTool implements SuperDictTool {

    private final static String KEY_PREFIX = "Dict:";

    @Autowired
    private RedissonCollection redissonCollection;

    @Autowired
    private AdminDictMapper adminDictMapper;


    @Override
    public <T> List<AdminDict> getList(SFunction<T, Object> function) {
        String parentCode = this.getKey(function);
        log.info(StrUtil.format("准备获取字典列表[{}]",parentCode));
        return getList(parentCode);
    }

    @Override
    public <T> String getText(SFunction<T, Object> function, int value) {
        String parentCode = this.getKey(function);
        log.info(StrUtil.format("准备获取字典[{}].[{}]",parentCode,value));
        return getText(parentCode,value);
    }

    @Override
    public String getText(Field function, int value) {
        String parentCode = this.getKey(function );
        log.info(StrUtil.format("准备获取字典[{}].[{}]",parentCode,value));
        return getText(parentCode,value);
    }


    public String getKey(Field function) {
        return this.getKey(function,false);
    }
    @Override
    @SneakyThrows(NoSuchFieldException.class)
    public String getKey(Field function, boolean dict) {
        Class<?> implClass = function.getDeclaringClass();
        String name = function.getName();
        //function.getDeclaringClass().getAnnotation()
        DictMapping annotation = implClass.getDeclaredField(name).getAnnotation(DictMapping.class);


        String str = (implClass.getSimpleName() + ":" + name).toUpperCase();
        if(dict){
            if(Assert.empty(annotation)){
               return "NONE";
            }else{
                return Assert.empty(annotation.value()) ? str : annotation.value();
            }
        }
        return Assert.empty(annotation) ? str : annotation.value();
    }

    @Override
    @SneakyThrows(NoSuchFieldException.class)
    public <T> String getKey(SFunction<T, Object> function){
        SerializedLambda resolve = LambdaUtils.resolve(function);
        String fieldName =  StringUtils.resolveFieldName(resolve.getImplMethodName());
        Class<?> implClass = resolve.getImplClass();
        DictMapping annotation = implClass.getDeclaredField(fieldName).getAnnotation(DictMapping.class);
        return Assert.empty(annotation) ? (implClass.getSimpleName() + ":" + fieldName).toUpperCase() : annotation.value();
    }


    private List<AdminDict> getList(String parentCode) {
        RMap<String, AdminDict> map = redissonCollection.getMap(KEY_PREFIX + parentCode);
        if(map.isEmpty()){
            log.warn(StrUtil.format("字典数据缓存查找没有找到，更新缓存[{}]...",parentCode));
            List<AdminDict> list = this.getListToCache(parentCode);
            if(Assert.empty(list)){
                log.warn(StrUtil.format("数据库没有数据，请检查是否添加字典[{}]...",parentCode));
                return list;
            }else{
                this.updateRedis(parentCode);
            }
            return this.getList(parentCode);
        }
        return new ArrayList<>(map.values());
    }




    private List<AdminDict> getListToCache(String parentCode) {
        AdminDict dict = adminDictMapper.selectOne(Wrappers.<AdminDict>query().lambda().eq(AdminDict::getCode,parentCode));
        if(Assert.empty(dict)){
            return new ArrayList<>();
        }
        Long id = dict.getId();
        LambdaQueryWrapper<AdminDict> wrapper = Wrappers.<AdminDict>query().lambda().eq(AdminDict::getParentId, id);
        List<AdminDict> list = adminDictMapper.selectList(wrapper);
        return list;
    }

    private boolean inDatabase(String parentCode, int value) {
        AdminDict dict = adminDictMapper.selectOne(Wrappers.<AdminDict>query().lambda().eq(AdminDict::getCode,parentCode + "_" + value));
        return Assert.notEmpty(dict);
    }

    private boolean inDatabase(String parentCode) {
        List<AdminDict> listToCache = getListToCache(parentCode);
        return Assert.notEmpty(listToCache);
    }


    private void updateRedis(String parentCode){
        //如果调用更新 则直接从数据库拿数据
        List<AdminDict> list = this.getListToCache(parentCode);
        this.updateRedis(parentCode,list);
    }

    private void updateRedis(String parentCode, List<AdminDict> list){
        //如果调用更新 则直接从数据库拿数据
        list.stream().map(this::convertToCacheMap)
                .forEach(x->redissonCollection.setMapValues(KEY_PREFIX + parentCode,x));
    }

    /**
     *
     * @param dict   {code:"sex_1",value:"1",text:"男"},{code:"sex_2",value:"2",text:"女"}
     * @return
     */
    private Map<String, AdminDict> convertToCacheMap(AdminDict dict){
        Map<String, AdminDict> map = new HashMap<>();
        map.put( dict.getCode() , dict);
        return map;
    }


    private String getText(String parentCode, int value) {
        RMap<String , AdminDict> map = redissonCollection.getMap(KEY_PREFIX + parentCode);
        if(map.isEmpty()){
            this.updateRedis(parentCode);
        }
        AdminDict o = map.get(parentCode + "_" + value);
        if(Assert.empty(o)){
            //数据库中是否存在  则同样需要更新缓存
            if(this.inDatabase(parentCode,value)){
                this.updateRedis(parentCode);
                return this.getText(parentCode,value);
            }
            return null;
        }
        return o.getText();
    }
}
